/**
 * 
 */
package org.ranch.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

/**
 * represents a user in the system
 *
 */
@Entity
@Table(name = "users")
@Inheritance(strategy = InheritanceType.JOINED)
public class User extends BaseData implements Comparable<User> {

    /**
     * The unique name for this user
     */
    private String username;
    /**
     * The secret word for this user
     */
    private String password;
    /**
     * The group of permissions granted to this user
     */
    private Set<Role> roles;
    /**
     * The secret question for this user. Very useful when retrieving the
     * password
     */
    private String secretQuestion;
    /**
     * The secret answer. Very useful when retrieving the password
     */
    private String secretAnswer;
    /**
     * The status of this user. e.g enabled, suspended
     */
    private UserStatus status;

    private String clearTextPassword;
    private String salt;

    /**
     * default constructor
     */
    public User() {
	super();
    }

    /**
     * constructor with initial specified values
     *
     * @param username
     * @param password
     * @param roles
     * @param secretQuestion
     * @param secretAnswer
     * @param status
     * @param clearTextPassword
     */
    public User(String username, String password, Set<Role> roles,
	    String secretQuestion, String secretAnswer, UserStatus status,
	    String clearTextPassword, String salt) {
	super();
	this.username = username;
	this.password = password;
	this.roles = roles;
	this.secretQuestion = secretQuestion;
	this.secretAnswer = secretAnswer;
	this.status = status;
	this.clearTextPassword = clearTextPassword;
	this.salt = salt;
    }

    /**
     * gets the clear text password. This is the password that is set by the
     * user possibly from the user interface
     *
     * @return the clearTextPassword
     */
    @Transient
    public String getClearTextPassword() {
	return clearTextPassword;
    }

    /**
     * sets the clear text password
     *
     * @see #getClearTextPassword()
     * @param clearTextPassword
     *            the clearTextPassword to set
     */
    public void setClearTextPassword(String clearTextPassword) {
	this.clearTextPassword = clearTextPassword;
    }

    /**
     * gets the username of the user
     *
     * @return the username
     */
    @Column(name = "username", nullable = false, unique = true)
    public String getUsername() {
	return username;
    }

    /**
     * sets the username
     *
     * @param username
     *            the username to set
     */
    public void setUsername(String username) {
	this.username = username;
    }

    /**
     * gets the password of the user. This is the hashed password
     *
     * @return the password
     */
    @Column(name = "password", nullable = false)
    public String getPassword() {
	return password;
    }

    /**
     * sets the password of the user
     *
     * @see #getPassword()
     * @param password
     *            the password to set
     */
    public void setPassword(String password) {
	this.password = password;
    }

    /**
     * gets the roles of the user
     *
     * @return the roles
     */
    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(name = "role_users", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    public synchronized Set<Role> getRoles() {
	synchronized (this) {
	    return roles;
	}
    }

    /**
     * sets the roles for the user
     *
     * @param roles
     *            the roles to set
     */
    public synchronized void setRoles(Set<Role> roles) {
	synchronized (this) {
	    this.roles = roles;
	}
    }

    /**
     * adds a Role to the list of roles mapped to this user
     *
     * @param role
     */
    public synchronized void addRole(Role role) {
	synchronized (this) {
	    if (roles == null) {
		roles = new HashSet<Role>();
	    }

	    if (!this.roles.contains(role)) {
		roles.add(role);
		role.addUser(this);
	    }
	}
    }

    /**
     * Removes a role from a set of roles mapped to this user *
     *
     * @param role
     */
    public synchronized void removeRole(Role role) {
	synchronized (this) {
	    if (roles != null) {
		for (Role r : roles) {
		    if (r.getName().equals(role.getName())) {
			roles.remove(role);
			break;
		    }
		}
	    }
	}
    }

    /**
     * gets the secret question of the user
     *
     * @return the secretQuestion
     */
    @Column(name = "secret_question", nullable = false)
    public String getSecretQuestion() {
	return secretQuestion;
    }

    /**
     * sets the secret question of the user
     *
     * @param secretQuestion
     *            the secretQuestion to set
     */
    public void setSecretQuestion(String secretQuestion) {
	this.secretQuestion = secretQuestion;
    }

    /**
     * gets the secret answer of the user
     *
     * @return the secretAnswer
     */
    @Column(name = "secret_answer", nullable = true)
    public String getSecretAnswer() {
	return secretAnswer;
    }

    /**
     * sets the secret answer of the user
     *
     * @param secretAnswer
     *            the secretAnswer to set
     */
    public void setSecretAnswer(String secretAnswer) {
	this.secretAnswer = secretAnswer;
    }

    /**
     * gets the status of the user i.e. whether the user is disabled, enabled
     *
     * @return the status
     */
    @Enumerated(EnumType.ORDINAL)
    @Column(name = "user_status", nullable = false)
    public UserStatus getStatus() {
	return status;
    }

    /**
     * sets the status of the user
     *
     * @see #getStatus()
     * @param status
     *            the status to set
     */
    public void setStatus(UserStatus status) {
	this.status = status;
    }

    /**
     * gets the salt used in determine the hashed password of the user
     *
     * @return the salt
     */
    @Column(name = "salt", nullable = false)
    public String getSalt() {
	return salt;
    }

    /**
     * sets the salt for the user
     *
     * @see #getSalt()
     * @param salt
     *            the salt to set
     */
    public void setSalt(String salt) {
	this.salt = salt;
    }

    /**
     * checks whether the user has a new password
     *
     * @return
     */
    public boolean hasNewPassword() {
	return (clearTextPassword != null && clearTextPassword.trim().length() > 0);
    }

    /**
     * gets a list of permissions for this user
     *
     * @return
     */
    public List<Permission> findPermissions() {
	List<Permission> permissions = null;
	if (roles != null && roles.size() > 0) {
	    permissions = new ArrayList<Permission>();
	    for (Role role : roles) {
		if (role.getPermissions() != null
			&& role.getPermissions().size() > 0) {
		    for (Permission perm : role.getPermissions()) {
			permissions.add(perm);
		    }
		}
	    }
	}
	return permissions;
    }

    /**
     * determines if the user has the default administrative role
     *
     * @return
     */
    public boolean hasAdministrativePrivileges() {
	if (roles != null) {
	    for (Role role : roles) {
		if (role.checkIfDefaultAdminRole()) {
		    return true;
		}
	    }
	}
	return false;
    }

    @Override
    public int compareTo(User o) {
	if (this.getUsername() == null || o.getUsername() == null)
	    return 0;
	return this.username.compareToIgnoreCase(o.username);
    }
}